// Copyright 2012 The Glue BSF Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('glue.Cache');
goog.provide('glue.Cache.ValidationFunctions')

goog.require('glue.EventType');
goog.require('glue.Event');

goog.require('goog.async.Deferred');
goog.require('goog.array');
goog.require('goog.storage.Storage');
goog.require('goog.json');
goog.require('goog.Timer');
goog.require('goog.iter.Iterator');
goog.require('goog.storage.mechanism.mechanismfactory');

/**
 * Cache abstraction it uses many different methods to reduce Ajax calls 
 * even across browser shutdown.
 * 
 * @param {Function} function call to see if object is out of date
 * 
 * @extends {goog.storage.Storage}
 * @constructor
 */
glue.Cache = function(model){
	var mechanism = goog.storage.mechanism.mechanismfactory.create();
	
	this.active_ = {};

	/** @type {array} An array of logical function to chain together on returned data. */
	this.logics_ = [];	

	goog.base(this,mechanism);
	this.model_ = model;

}
goog.inherits(glue.Cache, goog.storage.Storage);

/**
 * Adds a logic object to the model.
 * Logic objects add information such as Functions or additional Metadata to retrieved resources.
 * 
 * @param {Object} logic   
 */
glue.Cache.prototype.addLogic = function(logic){
	this.logics_.push(logic.get);
}

/**
 * Gets the key from the Cache
 * 
 * @param {String} key
 * 
 * @return {Object} the stored object  
 */
glue.Cache.prototype.get = function(key){
	if(this.active_.hasOwnProperty(key)){
		return this.active_[key];
	}else{
		var data = goog.base(this,'get',key);
		
		if(goog.isDefAndNotNull(data)){
			goog.array.forEach(this.logics_,goog.bind(function(logic){		
				data = logic(data,this);
			},this.model_));
			this.active_[key] = data;
		}
		return data;
	}
}

/**
 * Sets the cache.
 * 
 * @param {String} key
 * @param {Object} data
 */
glue.Cache.prototype.set = function(key,data){	
	if(!(data instanceof goog.async.Deferred)){		
		goog.array.forEach(this.logics_,goog.bind(function(logic){				
			data = logic(data,this);
		},this.model_));
		
		try{
			//this.mechanism.set(key,JSON.stringify(data))
			goog.base(this,'set',key,data);	
		}catch(err){
		
			//Fix to stop IE7 from crashing 	
		}
				
	}
	this.active_[key] = data;
}

/**
 * Removes the key from the permanent storage and the active storage.
 * 
 * @param {String} key 
 */
glue.Cache.prototype.remove = function(key){
	goog.base(this,'remove',key)
	if(this.active_.hasOwnProperty(key)){
		delete this.active_[key]
	}
}

/**
 * Checks to see if the cache contains this key
 * 
 * @param {String} key
 * 
 * @return {Bool}
 */
glue.Cache.prototype.containsKey = function(key){	
	return goog.isDefAndNotNull(this.get(key));
}

/**
 * Return a temporary holder.  And stores the temporary item in the cache if opt_set is true.
 * 
 * @param {string} url
 * @param {Bool=} opt_set defaults to false
 *  
 * @return {goog.async.Deferred}
 */
glue.Cache.prototype.setHolder = function(url,opt_set){
	var deferred = new goog.async.Deferred();
	if(!goog.isDefAndNotNull(opt_set)){
		opt_set = false;
	}
	
	if(opt_set){
		this.set(url,deferred);	
	}
	
	return deferred;
}

/** @override */
glue.Cache.prototype.clear = function(){	
	this.mechanism.clear();
	this.active_ = {};	
}

/** @override */
glue.Cache.prototype.toObject = function(){
	var returnObject = {'active':this.active_,'disk':{}};
	goog.array.forEach(goog.iter.toArray(this.mechanism.__iterator__(true)),goog.bind(function(url){
		returnObject['disk'][url] = this.mechanism.get(url);
	},this));
	return returnObject;
}

/** 
 * Sets the cache validation function
 * 
 * @param {Function} func Cache Validation Function
 */
glue.Cache.prototype.setCacheValidation = function(func){
	this.validationFunc_ = func;
}

/**
 * Function to revalidate the entire cache.
 */
glue.Cache.prototype.revalidate = function(){
	if(goog.isFunction(this.validationFunc_)){
		this.validationFunc_(this,this.model_);
	}
}

/**
 * Function that checks to see if an url is cached and 
 * if it is cached return the data to a callback function 
 * that will update the data and return it.
 * 
 * @param {String} url
 * @param {Function} func Function that takes 1 parameter the data and returns the updated data.
 */
glue.Cache.prototype.conditionalRevalidate = function(url,func){
	if(this.containsKey(url) && goog.isFunction(func)){
		this.set(url,func(this.get(url)));
		this.model_.dispatchEvent(new glue.Event(glue.EventType.REVALIDATE,[url]))
		
	}
}

/**
 * Generic Cache Validation functions. 
 * 
 * @enum {Function} 
 */
glue.Cache.ValidationFunctions = {
	noCache:function(cache,model){
		var invalidatedUrls = [];
		goog.array.forEach(goog.iter.toArray(cache.mechanism.__iterator__(true)),goog.bind(function(url){
			invalidatedUrls.push(url)
			this.remove(url);
		},cache))
		model.dispatchEvent(new glue.Event(glue.EventType.REVALIDATE,invalidatedUrls));
	}
}
